using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

/*  XBox360 To Flash Launcher
 * ===========================
 *      Made by joon.be
 * 
 * This code will help you create a game launcher that will do the following:
 * - launch whichever .EXE file you want (ideally a Flash projector)
 * - translate XBox 360 controls to keyboard controls
 * - play whicever game you want with your XBox controller(s)
 * 
 * The program made to be is 'invisible, meaning you won't be able to see 
 * it in your taskbar or when you alt+tab.
 * It is technically not just a launcher, as it remains running in the background. 
 * However it will exit itself the moment the game process is closed.
 * 
 * Have fun playing, 
 * 
 * contact me at: joon@joon.be or 
 * or on twitter: @joonturbo
 * check out the game for which this was initially developed here: LAZAKNITEZ.COM
 */

namespace be.joon.xbox360toflashlauncher
{
    public class Launcher : Microsoft.Xna.Framework.Game
    {
        #region API Imports

        // GetForeGroundWindow() is used to determine whether the game is the current focused window. 
        // If the game is 'pidlocked', the commands will only be sent to the game process.
        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

        // GetWindowThreadProcessId() is used to determind the pid of the active window.
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);

        // keybd_event() is used to simulate the keypress at the lowest possible level.
        [DllImport("user32.dll")]
        private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
        #endregion

        #region Constants
        // These are the ASCII byte values of some typical keys, 
        // you might need to add the one's that are missing
        const byte KeyArrowUp = 38;
        const byte KeyArrowLeft = 37;
        const byte KeyArrowRight = 39;
        const byte KeyArrowDown = 40;
        const byte KeyEnter = 13;
        const byte KeyEscape = 27;
        #endregion  

        #region Game variables
        GraphicsDeviceManager graphics;

        // These variables hold the previous state of all the controllers, 
        // so they can be compared with the current state.
        GamePadState previousStateP1;
        GamePadState previousStateP2;
        GamePadState previousStateP3;
        GamePadState previousStateP4;
        int pid;                            // The pid of the game process
        public enum StickDirection
        {
            UP, DOWN, LEFT, RIGHT
        }

        #endregion

        #region Settings
        const bool pidLocked = true;            // Whether or not the program should only send the controller commands
                                                // to the launched process. If set to false, it will send the controller 
                                                // commands to the current foreground process.
        bool autoLaunchGame = false;            // Whether or not the launcher should attempt to launch a game.
                                                // If set to false, the launcher just starts and never closes.
        const float stickThreshold = 0.1f;      // Determines how easy it is to use the analog stick for button press.
                                                // Value must be between 0 and 1.
                                                // If set to a low value, the stick will be very reactive. 
                                                // If set to a high value, the stick will have to be pushed all the way to the side to react.
        const String gameName = "LAZAKNITEZ";   // The name of the game/process to launch.
                                                // This file will have to reside in the same folder as the launcher.

        #endregion

        #region Constructor and Initialization
        public Launcher()
        {
            graphics = new GraphicsDeviceManager(this);
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            if (autoLaunchGame)
            {
                // Attempts to launch the game. 
                // Will throw an error if the executable is not found.
                try
                {
                    Process proc = System.Diagnostics.Process.Start(gameName);
                    pid = proc.Id;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Couldn't find EXECUTABLE.");
                    throw(e);
                }
            }
        }

        protected override void UnloadContent()
        {
        }
        #endregion

        #region Updates
        protected override void Update(GameTime gameTime)
        {
            UpdateInput();
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }

        void UpdateInput()
        {
            uint activePid = 0;

            // Find out what the active process is, but only if we're pidlocked
            if (pidLocked && autoLaunchGame)
            {
                GetWindowThreadProcessId(GetForegroundWindow(), out activePid);
            }

            // This is where the magic happens.
            // The HandleButton and HandleStick methods take care of all the translations.
            if (activePid == pid || !pidLocked)
            {
                // PLAYER ONE
                //------------
                GamePadState currentStateP1 = GamePad.GetState(PlayerIndex.One);
                if (currentStateP1.IsConnected)
                {
                    GamePadState current = currentStateP1;
                    GamePadState previous = previousStateP1;
                    HandleButton(current.Buttons.A,         previous.Buttons.A,         ConvertChar("P"));
                    HandleButton(current.Buttons.B,         previous.Buttons.B,         KeyArrowUp);
                    HandleButton(current.Buttons.X,         previous.Buttons.X,         KeyArrowUp);
                    HandleButton(current.Buttons.Y,         previous.Buttons.Y,         KeyArrowDown);
                    HandleButton(current.Buttons.Start,     previous.Buttons.Start,     KeyEnter);
                    HandleButton(current.Buttons.Back,      previous.Buttons.Back,      ConvertChar("Q"));
                    HandleButton(current.DPad.Left,         previous.DPad.Left,         KeyArrowLeft);
                    HandleButton(current.DPad.Right,        previous.DPad.Right,        KeyArrowRight);
                    HandleButton(current.DPad.Up,           previous.DPad.Up,           KeyArrowUp);
                    HandleButton(current.DPad.Down,         previous.DPad.Down,         KeyArrowDown);
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left,  KeyArrowLeft, StickDirection.LEFT);
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left,  KeyArrowRight, StickDirection.RIGHT);
                }
                previousStateP1 = currentStateP1;

                // PLAYER TWO
                //------------
                GamePadState currentStateP2 = GamePad.GetState(PlayerIndex.Two);
                if (currentStateP2.IsConnected)
                {
                    GamePadState current = currentStateP2;
                    GamePadState previous = previousStateP2;
                    HandleButton(current.Buttons.A,         previous.Buttons.A,         ConvertChar("S"));
                    HandleButton(current.Buttons.B,         previous.Buttons.B,         ConvertChar("W"));
                    HandleButton(current.Buttons.X,         previous.Buttons.X,         ConvertChar("W"));
                    HandleButton(current.Buttons.Y,         previous.Buttons.Y,         ConvertChar("S"));
                    HandleButton(current.DPad.Left,         previous.DPad.Left,         ConvertChar("A"));
                    HandleButton(current.DPad.Right,        previous.DPad.Right,        ConvertChar("D"));
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left,  ConvertChar("A"), StickDirection.LEFT);
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left,  ConvertChar("D"), StickDirection.RIGHT);
                }
                previousStateP2 = currentStateP2;

                // PLAYER THREE
                //--------------
                GamePadState currentStateP3 = GamePad.GetState(PlayerIndex.Three);
                if (currentStateP3.IsConnected)
                {
                    GamePadState current = currentStateP3;
                    GamePadState previous = previousStateP3;
                    HandleButton(current.Buttons.A,         previous.Buttons.A, ConvertChar("K"));
                    HandleButton(current.Buttons.B,         previous.Buttons.B, ConvertChar("I"));
                    HandleButton(current.Buttons.X,         previous.Buttons.X, ConvertChar("I"));
                    HandleButton(current.Buttons.Y,         previous.Buttons.Y, ConvertChar("K"));
                    HandleButton(current.DPad.Left,         previous.DPad.Left, ConvertChar("J"));
                    HandleButton(current.DPad.Right,        previous.DPad.Right, ConvertChar("L"));
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left, ConvertChar("J"), StickDirection.LEFT);
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left, ConvertChar("L"), StickDirection.RIGHT);
                }
                previousStateP3 = currentStateP3;

                // PLAYER FOUR
                //-------------
                GamePadState currentStateP4 = GamePad.GetState(PlayerIndex.Four);
                if (currentStateP4.IsConnected)
                {
                    GamePadState current = currentStateP4;
                    GamePadState previous = previousStateP4;
                    HandleButton(current.Buttons.A,         previous.Buttons.A, ConvertChar("G"));
                    HandleButton(current.Buttons.B,         previous.Buttons.B, ConvertChar("T"));
                    HandleButton(current.Buttons.X,         previous.Buttons.X, ConvertChar("T"));
                    HandleButton(current.Buttons.Y,         previous.Buttons.Y, ConvertChar("G"));
                    HandleButton(current.DPad.Left,         previous.DPad.Left, ConvertChar("F"));
                    HandleButton(current.DPad.Right,        previous.DPad.Right, ConvertChar("H"));
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left, ConvertChar("F"), StickDirection.LEFT);
                    HandleStick(current.ThumbSticks.Left,   previous.ThumbSticks.Left, ConvertChar("H"), StickDirection.RIGHT);
                }
                previousStateP4 = currentStateP4;
            }
            // If the app is pidlocked and the game process doesn't exist anymore,
            // We want to exit the launcher.
            else if(pidLocked && activePid != pid)
            {
                if (!IsGameStillRunning())
                {
                    Exit();
                }
            }
        }
        #endregion

        #region Utility methods
        // Converts the first character of a string to its byte value.
        private byte ConvertChar(String character)
        {
            return System.Text.Encoding.UTF8.GetBytes(character.ToCharArray())[0];
        }

        // Handles the key commands associated with the analog sticks
        private void HandleStick(Vector2 current, Vector2 previous, byte key, StickDirection direction)
        {
            switch (direction)
            {
                case StickDirection.DOWN:
                    if (current.Y < -stickThreshold && previous.Y >= -stickThreshold)
                    {
                        SendKeyPress(key);
                    }
                    else if (current.Y >= -stickThreshold && previous.Y < -stickThreshold)
                    {
                        SendKeyRelease(key);
                    }
                    break;
                case StickDirection.UP:
                    if (current.Y > stickThreshold && previous.Y <= stickThreshold)
                    {
                        SendKeyPress(key);
                    }
                    else if (current.Y <= stickThreshold && previous.Y > stickThreshold)
                    {
                        SendKeyRelease(key);
                    }
                    break;
                case StickDirection.RIGHT:
                    if (current.X > stickThreshold && previous.X <= stickThreshold)
                    {
                        SendKeyPress(key);
                    }
                    else if (current.X <= stickThreshold && previous.X > stickThreshold)
                    {
                        SendKeyRelease(key);
                    }
                    break;
                case StickDirection.LEFT:
                    if (current.X <= -stickThreshold && previous.X > -stickThreshold)
                    {
                        SendKeyPress(key);
                    }
                    else if (current.X > -stickThreshold && previous.X <= -stickThreshold)
                    {
                        SendKeyRelease(key);
                    }
                    break;
                default:
                    break;
            }
        }

        // Handles the key commands associated with the buttons, including DPAD
        private void HandleButton(ButtonState current, ButtonState previous, byte key)
        {
            if (current == ButtonState.Pressed &&
              previous == ButtonState.Released)
            {
                SendKeyPress(key);
            }
            else if (current == ButtonState.Released &&
                previous == ButtonState.Pressed)
            {
                SendKeyRelease(key);
            }
        }

        private void SendKeyPress(byte key)
        {
            keybd_event(key, 0, 0x01, new UIntPtr(0));
            Console.WriteLine("pressing " + key);
        }
        
        private void SendKeyRelease(byte key)
        {
            keybd_event(key, 0, 0x02, new UIntPtr(0));
            Console.WriteLine("releasing " + key);
        }
        
        // Determine whether the game is still running.
        private bool IsGameStillRunning()
        {
            try
            {
                Process proc = Process.GetProcessById(pid);
            }
            catch (ArgumentException e)
            {
                Console.WriteLine("Process was closed. Closing application. " + e.StackTrace);
                return false;
            }
            return true;
        }
        #endregion
    }
}
